#import <netinet/in.h>

#import "NetworkManager.h"

#import "GamePeer.h"
#import "GameClient.h"
#import "TCPConnection.h"
#import "ThrottledTCPConnection.h"
//#import "UDPSocket.h"
#import "NetUtilities.h"
#import "Multiplayer_Internal.h"

//CONSTANTS:

#define kResolveTimeOut				5.0
#define kMagic						0xABCD1234
#define kFutureTimeInterval			(50 * 365 * 24 * 3600.0)

//CLASS INTERFACES:

@interface GamePeer (Internal) <TCPConnectionDelegate>
@end

//FUNCTIONS:

/*
Game "identifier" is used as a Bonjour service type and is constrained by:
http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt
http://www.iana.org/assignments/port-numbers
*/
BOOL _IsValidGameIdentifier(NSString* string)
{
	static NSCharacterSet*		baseSet = nil;
	
	if(baseSet == nil)
	baseSet = [[[NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789"] invertedSet] copy];
	
	return (([string length] > 0) && ([string length] <= 14) && ([string rangeOfCharacterFromSet:baseSet].location == NSNotFound) ? YES : NO);
}

NSString* _GetDeviceUniqueID()
{
	static NSString*			uniqueID = nil;
	
	if(uniqueID == nil) {
#if ASPEN_SIMULATOR
		uniqueID = [[UIDevice currentDevice] model];
#else
		uniqueID = [[UIDevice currentDevice] uniqueIdentifier];
#endif
		uniqueID = [[[uniqueID uppercaseString] stringByReplacingOccurrencesOfString:@":" withString:@"-"] copy];
		if(uniqueID == nil)
		[NSException raise:NSInternalInconsistencyException format:@"Unable to retrieve unique ID"];
	}
	
	return uniqueID;
}

NSDate* _DistantFutureDate()
{
	static NSDate*				date = nil;
	
	if(date == nil)
	date = [[NSDate alloc] initWithTimeIntervalSinceReferenceDate:kFutureTimeInterval];
	
	return date;
}

BOOL _IsDistantFutureDate(NSDate* date)
{
	return (round([date timeIntervalSinceReferenceDate]) >= kFutureTimeInterval ? YES : NO);
}

//CLASS IMPLEMENTATIONS:

@implementation GamePeer

@synthesize server=_server, infoPlist=_plist, uniqueID=_uniqueID, name=_name, local=_local, delegate=_delegate, service=_service;


+ (GamePeer*) serverWithAddress:(NSString*)address
{
	return [[[GamePeer alloc] initWithName:nil address:IPAddressFromString(address)] autorelease];
}

+ (GamePeer*) serverWithIPv4Address:(UInt32)address port:(UInt16)port
{
	struct sockaddr_in		ipAddress;
	
	bzero(&ipAddress, sizeof(ipAddress));
	ipAddress.sin_len = sizeof(ipAddress);
	ipAddress.sin_family = AF_INET;
	ipAddress.sin_port = htons(port);
	ipAddress.sin_addr.s_addr = htonl(address);
	
	return [[[GamePeer alloc] initWithName:nil address:(struct sockaddr*)&ipAddress] autorelease];
}

- (id) initWithCFNetService:(CFNetServiceRef)netService
{
	NSString*					name = (netService ? (NSString*)CFNetServiceGetName(netService) : nil);
	struct sockaddr*			address;
	
	if(netService && CFNetServiceResolveWithTimeout(netService, kResolveTimeOut, NULL))
        address = (struct sockaddr*)[[(NSArray*)CFNetServiceGetAddressing(netService) objectAtIndex:0] bytes];
	else
        address = NULL;
	
	if((self = [self initWithName:name address:address]))
        _service = YES;
	
	return self;
}

- (id) initWithName:(NSString*)name address:(const struct sockaddr*)address
{
	NSRange						range;
	
	if(address == NULL) {
		[self release];
		return nil;
	}
	
	if((self = [super init])) {
		_server = YES;
		_address = malloc(address->sa_len);
		bcopy(address, _address, address->sa_len);
		_local = IPAddressIsLocal(_address);
		
		if(name) {
			range = [name rangeOfString:@":" options:0 range:NSMakeRange(0, [name length])];
			if(range.location == NSNotFound) {
				[self release];
				return nil;
			}
			_uniqueID = [[name substringToIndex:range.location] copy];
			_name = [[name substringFromIndex:(range.location + 1)] copy];
		}
	}
	
	return self;
}

- (id) initWithConnection:(TCPConnection*)connection
{
	if((self = [super init])) {
		_connection = [connection retain];
		[_connection setDelegate:self];
	}
	
	return self;
}

- (void) dealloc
{
	[self disconnect];
    
    [_plist release];
	[_name release];
	[_uniqueID release];
	
	if(_address)
	free(_address);
	
	[super dealloc];
}

- (BOOL) connect
{
	if(_connection)
        return NO;

    _connectionEstablished = NO;
    [NSThread detachNewThreadSelector:@selector(connectThreadedTCP) toTarget:self withObject:nil];
    BOOL ready = NO;
    while (ready == NO)
    {
        @synchronized (self) {
            ready = _connectionEstablished;
        }
    }
	if(_connection == nil)
        return NO;
	return YES;
}

- (void) connectThreadedTCP
{
    NSAutoreleasePool       * pool = [[NSAutoreleasePool alloc] init];
    
	_connection = [[ThrottledTCPConnection alloc] initWithRemoteAddress:_address];
	[_connection setDelegate:self];
    @synchronized (self){
        _connectionEstablished = YES;
    }
    _connectionRunLoop = CFRunLoopGetCurrent();
    [pool release];
    
    CFRunLoopRun();
}

- (BOOL) sendData:(NSData*)data
{
	return [_connection sendData:data];
}

- (BOOL) sendData:(NSData *)data throttledToBandwidthLimit:(float)kbs
{
    return [_connection sendData: data throttledToBandwidthLimit: kbs];
}

- (void) disconnect
{
	BOOL				wasValidating = (_connection ? YES : NO);
	
	if(_disconnecting == NO) {
		_disconnecting = YES;
		
        /*
		[_socket invalidate];
		[_socket setDelegate:nil];
		[_socket autorelease]; //HACK: Ensure UDPSocket is not de-alloced immediately as -disconnect might be called from inside one of its delegate calls
		_socket = nil;
        */
        
		[_connection invalidate];
        [_connection setDelegate:nil];
		[_connection autorelease]; //HACK: Ensure TCPConnection is not de-alloced immediately as -disconnect might be called from inside one of its delegate calls
		_connection = nil;
		
        if (_connectionRunLoop){
            CFRunLoopStop(_connectionRunLoop);
            _connectionRunLoop = nil;
        }
        
		if(_connectionComplete)
            [_delegate performSelectorOnMainThread:@selector(gamePeerDidDisconnect:) withObject:self waitUntilDone:YES];
		else if(wasValidating)
            [_delegate performSelectorOnMainThread:@selector(gamePeerDidFailConnecting:) withObject:self waitUntilDone:YES];
		
		_disconnecting = NO;
	}
}

- (UInt32) IPv4Address
{
	return (_address && (_address->sa_family == AF_INET) ? ntohl(((struct sockaddr_in*)_address)->sin_addr.s_addr) : 0);
}

- (NSString*) address
{
	return IPAddressToString(_address, YES, YES);
}

- (NSString*) addressWithoutPort
{
    return IPAddressToStringWithoutPort(_address, YES, YES);
}

- (BOOL) isConnecting
{
	return (_connectionEstablished && !_connectionComplete ? YES : NO);
}

- (BOOL) isConnected
{
	return _connectionComplete;
}

- (NSString*) description
{
	return [NSString stringWithFormat:@"<%@ = 0x%08X | ID = %@ | address = %@ | local = %i | name = \"%@\" | connected = %i>", [self class], (long)self, [self uniqueID], [self address], [self isLocal], [self name], [self isConnected]];
}

- (NSData*) _dataFromDictionary:(NSDictionary*)dictionary
{
	NSMutableData*				data = [NSMutableData data];
	int							magic = NSSwapHostIntToBig(kMagic);
	
	[data appendBytes:&magic length:sizeof(int)];
	[data appendData:[NSPropertyListSerialization dataFromPropertyList:dictionary format:NSPropertyListBinaryFormat_v1_0 errorDescription:NULL]];
	
	return data;
}

- (NSDictionary*) _dictionaryFromData:(NSData*)data
{
	NSDictionary*				dictionary = nil;
	NSString*					error;
	
	if(([data length] > sizeof(int)) && (NSSwapBigIntToHost(*((int*)[data bytes])) == kMagic)) {
		dictionary = [NSPropertyListSerialization propertyListFromData:[data subdataWithRange:NSMakeRange(sizeof(int), [data length] - sizeof(int))] mutabilityOption:NSPropertyListImmutable format:NULL errorDescription:&error];
		if(dictionary == nil)
		REPORT_ERROR(@"Failed de-serializing dictionary: \"%@\"", error);
	}
	
	return dictionary;
}

- (void) _finishConnecting:(NSDictionary*)dictionary
{
    
	[_uniqueID release];
	_uniqueID = [[dictionary objectForKey:@"id"] copy];
	[_name release];
	_name = [[dictionary objectForKey:@"name"] copy];
	_plist = [[dictionary objectForKey:@"info"] retain];
    
	if(![_uniqueID length] || ![_name length])
        [NSException raise:NSInternalInconsistencyException format:@""];
	
    _connectionComplete = YES;
    /*
	_socket = [[UDPSocket alloc] initWithPort:[_connection localPort]];
	if(_socket == nil) {
		REPORT_ERROR(@"Failed creating UDP socket on port %i", [_connection localPort]);
		[self disconnect];
	}
	else {
		[_socket setDelegate:self]; 
    */
        [_delegate performSelectorOnMainThread:@selector(gamePeerDidConnect:) withObject:self waitUntilDone:YES];
	//}
}

- (NSTimeInterval) measureRoundTripLatency
{
	CFTimeInterval				time;
	NSData*						data;
	NSDictionary*				dictionary;
	
	data = [self _dataFromDictionary:[NSDictionary dictionary]];
	
	time = CFAbsoluteTimeGetCurrent();
	if(![_connection sendData:data])
        return -1.0;
	do {
		data = [_connection receiveData];
		if(data == nil)
            return -1.0;
		
		dictionary = [self _dictionaryFromData:data];
		if(dictionary) {
			if([dictionary count]) {
				REPORT_ERROR(@"Received unexpected dictionary: \"%@\"", dictionary);
				dictionary = nil;
			}
		}
		else
            [_delegate performSelectorOnMainThread:@selector(gamePeerDidReceiveData:) withObject:data waitUntilDone:YES];
            
	} while(dictionary == nil);
	time = CFAbsoluteTimeGetCurrent() - time;
	
	return time;
}

- (void) connectionDidFailOpening:(TCPConnection*)connection
{
	[self disconnect];
}

- (void) connectionDidOpen:(TCPConnection*)connection
{
	NSDictionary*				dictionary;
	NSData*						data;
	const struct sockaddr*		address;
	
	if(_address == NULL) {
		address = [connection remoteSocketAddress];
		if(address == NULL) {
			REPORT_ERROR(@"Failed retrieving connection remote address from %@", connection);
			[self disconnect];
			return;
		}
		_address = malloc(address->sa_len);
		bcopy(address, _address, address->sa_len);
		_local = IPAddressIsLocal(_address);
	}
	
	dictionary = [NSMutableDictionary new];
	[(NSMutableDictionary*)dictionary setObject:_GetDeviceUniqueID() forKey:@"id"];
	[(NSMutableDictionary*)dictionary setObject:[[NetworkManager sharedManager] networkName] forKey:@"name"];
	[(NSMutableDictionary*)dictionary setValue:[[NetworkManager sharedManager] networkPlist] forKey:@"info"];
	data = [self _dataFromDictionary:dictionary];
	[dictionary release];
	
	if(_server == NO) {
		dictionary = [self _dictionaryFromData:[connection receiveData]];
		if(dictionary == nil) {
			REPORT_ERROR(@"Failed receiving dictionary from connection", NULL);
			[self disconnect];
			return;
		}
	}
	
	if(![connection sendData:data]) {
		REPORT_ERROR(@"Failed sending dictionary to connection", NULL);
		[self disconnect];
	}
	
	if(_server == NO)
        [self _finishConnecting:dictionary];
}

- (void) connectionDidClose:(TCPConnection*)connection
{
	[self disconnect];
}

- (void) connection:(TCPConnection*)connection didReceiveData:(NSData*)data
{
	NSDictionary*				dictionary;
	
	
    if((dictionary = [self _dictionaryFromData:data])) {
		if(!_connectionComplete)
            [self _finishConnecting:dictionary];
		else if(![dictionary count]) {
			if(![_connection sendData:data])
                REPORT_ERROR(@"Failed sending dictionary: \"%@\"", dictionary);
		}
		else
            REPORT_ERROR(@"Received unexpected dictionary: \"%@\"", dictionary);
	}
	else
        [_delegate performSelectorOnMainThread:@selector(gamePeerDidReceiveData:) withObject:data waitUntilDone:YES];
    
    
}

/*
- (void) socketDidInvalidate:(UDPSocket*)socket
{
	[self disconnect];
}
- (void) socket:(UDPSocket*)socket didReceiveData:(NSData*)data fromRemoteAddress:(const struct sockaddr*)address
{
	
    
    const struct sockaddr*				remoteAddress = [_connection remoteSocketAddress];
	
    if(address && remoteAddress && (address->sa_len == remoteAddress->sa_len) && !bcmp(address, remoteAddress, address->sa_len))
        [_delegate performSelectorOnMainThread:@selector(gamePeerDidReceiveData:) withObject:data waitUntilDone:YES];
	else
        REPORT_ERROR(@"Received UDP data from unknown sender at \"%@\"", IPAddressToString(address, YES, NO));
    
    
}
*/


@end
